package net_msg

import (
	"encoding/binary"
	"io"

	"gitee.com/ameise84/e3utils/bytes_buffer"
	"gitee.com/ameise84/e3utils/errors"
	"github.com/sourcegraph/conc/panics"
	"servers/compress"
	"servers/packet"
)

const (
	ZipSize = compress.ZipSize
)

type msg struct {
	ReqID     uint64
	ServerID  string
	ProtoName string
	ProtoBody []byte
}

func (m *msg) SetMsg(id uint64, srvID string, name string, body []byte) {
	m.ReqID = id
	m.ServerID = srvID
	m.ProtoName = name
	m.ProtoBody = body
}

func (m *msg) Marshal() []byte {
	b := bytes_buffer.NewShiftBuffer(1024, 0, true)
	packet.PackUint64(&b, binary.BigEndian, m.ReqID)
	packet.PackString(&b, binary.BigEndian, m.ServerID)
	packet.PackString(&b, binary.BigEndian, m.ProtoName)
	packet.PackBytes(&b, binary.BigEndian, m.ProtoBody)
	data, _ := b.Peek()
	dataLen := len(data)
	var zOut []byte
	isZip := uint8(0)
	if dataLen > ZipSize {
		zOut, _ = zip(data)
		if len(zOut) >= dataLen {
			zOut = data
		} else {
			isZip = 1
		}
	} else {
		zOut = data
	}
	cOut := make([]byte, len(zOut)+32)
	cdata, _ := encrypt(zOut, cOut[9:])
	cLen := len(cdata)
	binary.BigEndian.PutUint32(cOut[:4], uint32(cLen))
	binary.BigEndian.PutUint32(cOut[4:8], uint32(dataLen))
	cOut[8] = isZip
	return cOut[:cLen+9]
}

func (m *msg) Unmarshal(src []byte) (err error) {
	if len(src) < 8 {
		return errors.WrapWithStack(io.ErrShortBuffer, "Unmarshal")
	}
	cLen := binary.BigEndian.Uint32(src[:4])
	dataLen := binary.BigEndian.Uint32(src[4:8])
	isZip := src[8] == 1
	cOut := make([]byte, cLen)
	if cOut, err = decrypt(src[9:], cOut); err != nil {
		return errors.WrapWithStack(err, "Unmarshal")
	}
	zOut := make([]byte, dataLen)
	if isZip {
		if zOut, err = unzip(cOut); err != nil {
			return errors.WrapWithStack(err, "Unmarshal")
		}
	} else {
		zOut = cOut
	}

	rs := panics.Try(func() {
		b := bytes_buffer.Warp(zOut)
		reqID := packet.UnPackUint64(b, binary.BigEndian)
		srvID := packet.UnPackString(b, binary.BigEndian)
		name := packet.UnPackString(b, binary.BigEndian)
		pb := packet.UnPackBytes(b, binary.BigEndian)
		m.SetMsg(reqID, srvID, name, pb)
	})
	return rs.AsError()
}

func (m *msg) GetID() uint64 {
	return m.ReqID
}

func (m *msg) GetSrvID() string {
	return m.ServerID
}

func (m *msg) GetName() string {
	return m.ProtoName
}

func (m *msg) GetBody() []byte {
	return m.ProtoBody
}
